import DataStructure.DizkData;
import DataStructure.ProofData;
import DataStructure.decodeData;
import DataStructure.hashMxGenData;
import algebra.curves.barreto_naehrig.*;
import algebra.curves.barreto_naehrig.abstract_bn_parameters.AbstractBNG1Parameters;
import algebra.curves.barreto_naehrig.abstract_bn_parameters.AbstractBNG2Parameters;
import algebra.curves.barreto_naehrig.abstract_bn_parameters.AbstractBNGTParameters;
import algebra.curves.barreto_naehrig.bn254a.BN254aFields;
import algebra.curves.barreto_naehrig.bn254a.BN254aG1;
import algebra.curves.barreto_naehrig.bn254a.BN254aG2;
import algebra.curves.barreto_naehrig.bn254a.BN254aPairing;
import algebra.curves.barreto_naehrig.bn254a.bn254a_parameters.BN254aG1Parameters;
import algebra.curves.barreto_naehrig.bn254a.bn254a_parameters.BN254aG2Parameters;
import com.google.zxing.common.reedsolomon.GenericGF;
import com.google.zxing.common.reedsolomon.ReedSolomonDecoder;
import com.google.zxing.common.reedsolomon.ReedSolomonEncoder;
import com.google.zxing.common.reedsolomon.ReedSolomonException;
import configuration.Configuration;
import it.unisa.dia.gas.jpbc.*;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.spark.SparkConf;
import org.apache.spark.api.java.JavaPairRDD;
import org.apache.spark.api.java.JavaSparkContext;
import org.apache.spark.storage.StorageLevel;
import profiler.generation.R1CSConstruction;
import profiler.utils.SparkUtils;
import relations.objects.Assignment;
import relations.r1cs.R1CSRelation;
import relations.r1cs.R1CSRelationRDD;
import scala.Tuple3;
import zk_proof_systems.zkSNARK.*;
import zk_proof_systems.zkSNARK.objects.CRS;
import zk_proof_systems.zkSNARK.objects.Proof;

import java.io.*;
import java.math.BigInteger;
import java.util.HashMap;
import java.util.Map;


public class Scheme {
    int chunkNum;
    int blockNum;
    Pairing pairing;
    Element _g;
    Element _h;
    Element _xi;
    Element epsilon;
    byte[][][] _Dx;
    Element[] phi;
    Element[] eta;
    Element[] _theta;
    Element _acc;
    Element _Dacc;

    //初始设置参数
    public void setup(byte[][][] Mx) {
        int row = Mx.length;
        int collumn = Mx[0].length;
        this.chunkNum = row;
        this.blockNum = collumn;
        this.pairing = Arithmetic.pairing;
        this._g = pairing.getG1().newElement().setToRandom().getImmutable();
        this._xi = pairing.getZr().newElement().setToRandom().getImmutable();
        this._Dx = Mx;
        this.phi = new Element[collumn];
        for (int i = 0; i < collumn; i++) {
            Element newi = pairing.getZr().newElement().set(i);
            Element phi_i = pairing.getG1().newElement().set(_g).powZn(pairing.getZr().newElement().set(_xi).powZn(newi)).getImmutable();
            phi[i] = phi_i;
        }
        this.eta = new Element[row];
        for (int i = 0; i < row; i++) {
            Element newi = pairing.getZr().newElement().set(i);
            Element epsilon_i = pairing.getG1().newElement().set(_h).powZn(pairing.getZr().newElement().set(epsilon).powZn(newi)).getImmutable();
            eta[i] = epsilon_i;
        }
        this._theta = new Element[chunkNum];
        for (int i = 0; i < chunkNum; i++) {
            this._theta[i] = pairing.getZr().newElement().setToZero().getImmutable();
        }
    }

    //纠删码编码
    public byte[][][] encode(byte[][][] Mx) {
        GenericGF gf = new GenericGF(285, 256, 0);
        ReedSolomonEncoder encoder = new ReedSolomonEncoder(gf);
        byte[][][] newMx = new byte[init.chunckNum][init.blockNum][init.bitNum + init.parityShards];
        this.pairing = Arithmetic.pairing;
        this._h = pairing.getG2().newElement().setToRandom().getImmutable();
        this.epsilon = pairing.getZr().newElement().setToRandom().getImmutable();
        int row = Mx.length;
        this.eta = new Element[row];
        for (int i = 0; i < row; i++) {
            Element newi = pairing.getZr().newElement().set(i);
            Element epsilon_i = pairing.getG1().newElement().set(_h).powZn(pairing.getZr().newElement().set(epsilon).powZn(newi)).getImmutable();
            eta[i] = epsilon_i;
        }
        Element prod = pairing.getZr().newElement().setToOne().getImmutable();
        for (int i = 0; i < Mx.length; i++) {
            for (int j = 0; j < Mx[i].length; j++) {
                //acc
                Element HData = pairing.getZr().newElement().setFromHash(Mx[i][j], 0, Mx[i][j].length).getImmutable();
                Element HData_epsilon = pairing.getZr().newElement().set(HData).add(this.epsilon).getImmutable();
                prod = prod.mul(HData_epsilon);
                //ReedSolomon
                int[] intTemp = new int[init.bitNum + init.parityShards];
                //byte to int
                for (int m = 0; m < Mx[i][j].length; m++) {
                    intTemp[m] = Mx[i][j][m] & 0xff;
                }
                encoder.encode(intTemp, init.parityShards);

                //int to byte
                for (int n = 0; n < intTemp.length; n++) {
                    newMx[i][j][n] = (byte) intTemp[n];
                }
            }
        }

        this._Dacc = pairing.getG2().newElement().set(this._h).powZn(prod);
        return newMx;
    }


    //生成标签
    public void tag_Gen() {
        byte[][][] Mx = this._Dx;
        Element[][] HMx = new Element[this.chunkNum][this.blockNum];
        //对每个block计算哈希，保存到HMx中
        for (int i = 0; i < this.chunkNum; i++) {
            for (int j = 0; j < this.blockNum; j++) {
                //use hash and set it to Zr
                HMx[i][j] = pairing.getZr().newElement().setFromHash(Mx[i][j], 0, Mx[i][j].length).getImmutable();
            }
        }
        //利用哈希计算标签
        for (int i = 0; i < Mx.length; i++) {
            Element Lixi = Arithmetic.pbcPolynomialiEval(HMx, this._xi, i).getImmutable();
            Element g_Lixi = pairing.getG1().newElement().set(this._g).powZn(Lixi).getImmutable();
            this._theta[i] = g_Lixi;
        }
        //计算累加器，用于验证标签
        Element prod = pairing.getZr().newElement().setToOne().getImmutable();
        for (int i = 0; i < this._theta.length; i++) {
            Element ZHtheta = pairing.getZr().newElement().setFromHash(this._theta[i].toBytes(), 0, this._theta[i].getLengthInBytes()).getImmutable();
            Element ZHtheta_epsilon = pairing.getZr().newElement().set(ZHtheta).add(this.epsilon).getImmutable();
            prod = prod.mul(ZHtheta_epsilon);
        }
        this._acc = pairing.getG2().newElement().set(this._h).powZn(prod);
    }

    //纠删码解码
    public decodeData decodeMx(Map<Integer, Element> chal) throws ReedSolomonException {
        //decode
        GenericGF gf = new GenericGF(285, 256, 0);
        ReedSolomonDecoder decoder = new ReedSolomonDecoder(gf);
        byte[][][] Mx = this._Dx;
        byte[][][] realMx = new byte[init.chunckNum][init.blockNum][init.bitNum];
        for (int i : chal.keySet()) {
            for (int j = 0; j < Mx[i].length; j++) {
                int[] intTemp = new int[init.bitNum + init.parityShards];
                byte[] realBlock = new byte[init.bitNum];
                for (int k = 0; k < Mx[i][j].length; k++) {
                    intTemp[k] = Mx[i][j][k] & 0xff;
                }
                decoder.decode(intTemp, init.parityShards);
                for (int m = 0; m < realBlock.length; m++) {
                    realBlock[m] = (byte) intTemp[m];
                }
                realMx[i][j] = realBlock;
            }
        }
        Element wit = pairing.getG2().newElement().setToOne().getImmutable();
        Map<Integer, byte[]> dictD_C = new HashMap<>();
        for (int i = 0; i < this.chunkNum; i++) {
            for (int j = 0; j < this.blockNum; j++) {
                dictD_C.put(i * chunkNum + j, Mx[i][j]);
            }
        }
        for (int i = 0; i < Mx.length; i++) {
            for (int j = 0; j < this.blockNum; j++) {
                dictD_C.remove(i * chunkNum + j);
            }
        }
        Element[] prodD_C = new Element[1];
        prodD_C[0] = pairing.getZr().newElement().setToOne();
        for (int i : dictD_C.keySet()) {
            Element[] slice = new Element[2];
            slice[0] = pairing.getZr().newElement().setFromHash(dictD_C.get(i), 0, dictD_C.get(i).length);
            slice[1] = pairing.getZr().newElement().setToOne();
            prodD_C = Arithmetic.polynomialMul(prodD_C, slice);
        }
        for (int i = 0; i < prodD_C.length; i++) {
            wit = wit.mul(this.eta[i].powZn(prodD_C[i]));
        }
        Element prod2 = pairing.getZr().newElement().setToOne().getImmutable();
        for (int i = 0; i < Mx.length; i++) {
            for (int j = 0; j < realMx[i].length; j++) {
                Element zDatai = pairing.getZr().newElement().setFromHash(realMx[i][j], 0, realMx[i][j].length).getImmutable();
                Element zDatai_epsilon = zDatai.add(epsilon);
                prod2 = prod2.mul(zDatai_epsilon);
            }
        }
        return new decodeData(wit, prod2);
    }

    //累加器验证数据块
    public void read( Element wit, Element prod2) throws ReedSolomonException {
        //acc : no challenge
        long t_read_acc = System.nanoTime();
        Element left1 = pairing.pairing(this._Dacc, this._h).getImmutable();
        Element e4 = pairing.getG2().newElement().set(this._h).powZn(prod2);
        Element right1 = pairing.pairing(wit, e4);
        long t_read_acc_end = System.nanoTime();
        System.out.println("read_acc time:" + (double) (t_read_acc_end - t_read_acc) / 1000000000.0);
        System.out.println("data acc " + left1.isEqual(right1));
    }

    //生成挑战
    public Map<Integer, Element> challenge(int CNum) {
        if (CNum < 0 || CNum > this.chunkNum + 1) {
            System.out.println("CNum wrong!");
        }
        int[] selectedBlockSet = Arithmetic.getUniqueRandomNum(this.chunkNum, CNum);
        Map<Integer, Element> chal = new HashMap<>();
        for (int i : selectedBlockSet) {
            chal.put(i, pairing.getZr().newElement().setToRandom().getImmutable());
        }
        return chal;
    }

    public String sha256(byte[] data) {
        byte[] res = DigestUtils.sha256(data);
        BigInteger hashInt = new BigInteger(1, res);
        return hashInt.toString();
    }

    //模拟zksnark计算过程，用于之后的计算（这部分开销不算在内）
    public hashMxGenData hashMxGen(Map<Integer, Element> chal) {
        byte[][][] hashMx = this._Dx;
        Element[][] HMx = new Element[this.chunkNum][this.blockNum];
        Element HMx_all = pairing.getZr().newElement().setToZero();
        Element temp = pairing.getZr().newElement().setToZero();
//        BigInteger[][] HMx = new BigInteger[this.chunkNum][this.blockNum];
//        BigInteger HMx_all = BigInteger.ZERO;
//       BigInteger temp = BigInteger.ZERO;
        for (Integer i : chal.keySet()) {
            for (int j = 0; j < hashMx[i].length; j++) {
                Element hashVal = pairing.getZr().newElement().setFromHash(hashMx[i][j], 0, hashMx[i][j].length);
                Element juhe = pairing.getZr().newElement().set(chal.get(i)).mul(hashVal);
                HMx[i][j] = juhe;
                HMx_all.add(juhe);
                temp.add(juhe);
//                String hashVal = sha256(hashMx[i][j]);
//                BigInteger BigHashVal = new BigInteger(hashVal);
//                Element juhe = pairing.getZr().newElement().set(chal.get(i)).mul(BigHashVal);
//                HMx[i][j] = juhe;
//                HMx_all = HMx_all.add(juhe);
//                temp = temp.add(juhe);
            }

        }
        return new hashMxGenData(HMx, HMx_all, chunkNum, blockNum);
    }

    //存储节点生成证明
    public ProofData proofGen(Map<Integer, Element> chal, Element[][] HMx) {
        Element[] Lx_sum = new Element[this.blockNum];
        for (int i = 0; i < this.blockNum; i++) {
            Lx_sum[i] = pairing.getZr().newElement().setToZero().getImmutable();
        }
        for (int i : chal.keySet()) {
            for (int j = 0; j < this.blockNum; j++) {
                Element newHMx = pairing.getZr().newElement().set(HMx[i][j]).getImmutable();
                Lx_sum[j] = Lx_sum[j].add(newHMx).getImmutable();
            }
        }
        Element wit = pairing.getG2().newElement().setToOne().getImmutable();
        Map<Integer, Element> theta_prf = new HashMap<>();
        Map<Integer, Element> dictD_C = new HashMap<>();
        for (int i = 0; i < this.chunkNum; i++) {
            dictD_C.put(i, this._theta[i]);
        }
        for (int i : chal.keySet()) {
            dictD_C.remove(i);
            theta_prf.put(i, this._theta[i]);
        }
        Element[] prodD_C = new Element[1];
        prodD_C[0] = pairing.getZr().newElement().setToOne();
        for (int i : dictD_C.keySet()) {
            Element[] slice = new Element[2];
            slice[0] = pairing.getZr().newElement().setFromHash(dictD_C.get(i).toBytes(), 0, dictD_C.get(i).getLengthInBytes());
            slice[1] = pairing.getZr().newElement().setToOne();
            prodD_C = Arithmetic.polynomialMul(prodD_C, slice);
        }

        for (int i = 0; i < prodD_C.length; i++) {
            wit = wit.mul(this.eta[i].powZn(prodD_C[i]));
        }
        return new ProofData(theta_prf, wit, Lx_sum);
    }

    //智能合约验证证明（合约gas开销在remix上跑一下）
    public boolean verify(Map<Integer, Element> chal, Map<Integer, Element> theta_prf, Element wit, Element[] Lx_sum) {
        Element left1 = pairing.pairing(this._acc, this._h).getImmutable();
        Element prod = pairing.getZr().newElement().setToOne().getImmutable();
        Element left2 = pairing.getG1().newElement().setToOne().getImmutable();
        for (int i : chal.keySet()) {
            Element zthetai = pairing.getZr().newElement().setFromHash(this._theta[i].toBytes(), 0, this._theta[i].getLengthInBytes()).getImmutable();
            Element zthetai_epsilon = zthetai.add(epsilon);
            prod = prod.mul(zthetai_epsilon);
            left2 = left2.mul(pairing.getG1().newElement().set(theta_prf.get(i)).powZn(chal.get(i)));
        }
        Element e4 = pairing.getG2().newElement().set(this._h).powZn(prod);
        Element right1 = pairing.pairing(wit, e4);
        System.out.println("pair1 -> " + left1.isEqual(right1));
        Element right2 = pairing.getG1().newElement().setToOne().getImmutable();
        for (int i = 0; i < this.blockNum; i++) {
            right2 = right2.mul(pairing.getG1().newElement().set(this.phi[i]).powZn(Lx_sum[i]));
        }
        Element px = Arithmetic.polynomialEval(Lx_sum, this._xi);
        Element g_px = pairing.getG1().newElement().set(this._g).powZn(px);

        //System.out.println("3:"+px.isEqual(Arithmetic.polynomialEval(Lx_sum, this._xi)));

        //System.out.println("g_px.isEqual(right2) "+g_px.isEqual(right2));

        return left2.isEqual(right2);
    }

    //dizk设置参数（不并行）
    public Configuration setUp() {
        Configuration config;
        config = new Configuration();
        config.setRuntimeFlag(false);
        config.setDebugFlag(true);
        return config;
    }

    //设置spark参数
    private transient JavaSparkContext sc;
    Configuration config;

    //dizk设置参数（并行）
    public void sparkSetup() {
        final SparkConf conf = new SparkConf().setMaster("local["+init.numExecutors+"]").setAppName("default");
        conf.set("spark.files.overwrite", "true");
        conf.set("spark.serializer", "org.apache.spark.serializer.KryoSerializer");
        conf.registerKryoClasses(SparkUtils.zksparkClasses());

        sc = new JavaSparkContext(conf);

        config = new Configuration(init.numExecutors, 1, 1, init.numPartitions, sc, StorageLevel.MEMORY_ONLY());
        config.setRuntimeFlag(false);
        config.setDebugFlag(true);
    }


    //zksnark
    public static <BNFrT extends BNFields.BNFr<BNFrT>,
            BNFqT extends BNFields.BNFq<BNFqT>,
            BNFq2T extends BNFields.BNFq2<BNFqT, BNFq2T>,
            BNFq6T extends BNFields.BNFq6<BNFqT, BNFq2T, BNFq6T>,
            BNFq12T extends BNFields.BNFq12<BNFqT, BNFq2T, BNFq6T, BNFq12T>,
            BNG1T extends BNG1<BNFrT, BNFqT, BNG1T, BNG1ParametersT>,
            BNG2T extends BNG2<BNFrT, BNFqT, BNFq2T, BNG2T, BNG2ParametersT>,
            BNGTT extends BNGT<BNFqT, BNFq2T, BNFq6T, BNFq12T, BNGTT, BNGTParametersT>,
            BNG1ParametersT extends AbstractBNG1Parameters<BNFrT, BNFqT, BNG1T, BNG1ParametersT>,
            BNG2ParametersT extends AbstractBNG2Parameters<BNFrT, BNFqT, BNFq2T, BNG2T, BNG2ParametersT>,
            BNGTParametersT extends AbstractBNGTParameters<BNFqT, BNFq2T, BNFq6T, BNFq12T, BNGTT, BNGTParametersT>,
            BNPublicParametersT extends BNPublicParameters<BNFqT, BNFq2T, BNFq6T, BNFq12T>,
            BNPairingT extends BNPairing<BNFrT, BNFqT, BNFq2T, BNFq6T, BNFq12T, BNG1T, BNG2T, BNGTT, BNG1ParametersT, BNG2ParametersT, BNGTParametersT, BNPublicParametersT>>
    DizkData SerialBNProofSystem(
            Configuration config,
            BNFrT fieldFactory,
            BNG1T g1Factory,
            BNG2T g2Factory,
            BNPairingT pairing,
            byte[][][] Mx,
            Map<Integer, Element> chal) {
        final Tuple3<R1CSRelation<BNFrT>, Assignment<BNFrT>, Assignment<BNFrT>> construction =
                R1CSConstruction.newtest(fieldFactory, config, Mx, chal);
        final R1CSRelation<BNFrT> r1cs = construction._1();
        final Assignment<BNFrT> primary = construction._2();
        final Assignment<BNFrT> fullAssignment = construction._3();

        long t_zkSetup = System.nanoTime();
        final CRS<BNFrT, BNG1T, BNG2T, BNGTT> CRS =
                SerialSetup.generate(r1cs, fieldFactory, g1Factory, g2Factory, pairing, config);
        long t_zkSetup_end = System.nanoTime();
        System.out.println("zkSetup time:" + (double) (t_zkSetup_end - t_zkSetup) / 1000000000.0 + "s");
        double t_zkSetup_time = (double) (t_zkSetup_end - t_zkSetup) / 1000000000.0;

        long t_zkProof = System.nanoTime();
        final Proof<BNG1T, BNG2T> proof =
                SerialProver.prove(CRS.provingKey(), primary, fullAssignment, fieldFactory, config);
        long t_zkProof_end = System.nanoTime();
        System.out.println("zkProof time:" + (double) (t_zkProof_end - t_zkProof) / 1000000000.0 + "s");
        double t_zkProof_time = (double) (t_zkProof_end - t_zkProof) / 1000000000.0;

        long t_zkVerify = System.nanoTime();
        final boolean isValid = Verifier.verify(CRS.verificationKey(), primary, proof, pairing, config);
        long t_zkVerify_end = System.nanoTime();
        System.out.println("zkVerify time:" + (double) (t_zkVerify_end - t_zkVerify) / 1000000000.0 + "s");
        double t_zkVerify_time = (double) (t_zkVerify_end - t_zkVerify) / 1000000000.0;

        System.out.println(isValid);
        return new DizkData(t_zkSetup_time, t_zkProof_time, t_zkVerify_time);
    }

    //dizk
    public static <BNFrT extends BNFields.BNFr<BNFrT>,
            BNFqT extends BNFields.BNFq<BNFqT>,
            BNFq2T extends BNFields.BNFq2<BNFqT, BNFq2T>,
            BNFq6T extends BNFields.BNFq6<BNFqT, BNFq2T, BNFq6T>,
            BNFq12T extends BNFields.BNFq12<BNFqT, BNFq2T, BNFq6T, BNFq12T>,
            BNG1T extends BNG1<BNFrT, BNFqT, BNG1T, BNG1ParametersT>,
            BNG2T extends BNG2<BNFrT, BNFqT, BNFq2T, BNG2T, BNG2ParametersT>,
            BNGTT extends BNGT<BNFqT, BNFq2T, BNFq6T, BNFq12T, BNGTT, BNGTParametersT>,
            BNG1ParametersT extends AbstractBNG1Parameters<BNFrT, BNFqT, BNG1T, BNG1ParametersT>,
            BNG2ParametersT extends AbstractBNG2Parameters<BNFrT, BNFqT, BNFq2T, BNG2T, BNG2ParametersT>,
            BNGTParametersT extends AbstractBNGTParameters<BNFqT, BNFq2T, BNFq6T, BNFq12T, BNGTT, BNGTParametersT>,
            BNPublicParametersT extends BNPublicParameters<BNFqT, BNFq2T, BNFq6T, BNFq12T>,
            BNPairingT extends BNPairing<BNFrT, BNFqT, BNFq2T, BNFq6T, BNFq12T, BNG1T, BNG2T, BNGTT, BNG1ParametersT, BNG2ParametersT, BNGTParametersT, BNPublicParametersT>>
    DizkData DistributedBNProofSystem(
            Configuration config,
            BNFrT fieldFactory,
            BNG1T g1Factory,
            BNG2T g2Factory,
            BNPairingT pairing,
            byte[][][] Mx,
            Map<Integer, Element> chal) throws IOException {

        final Tuple3<R1CSRelationRDD<BNFrT>, Assignment<BNFrT>, JavaPairRDD<Long, BNFrT>> construction =
                R1CSConstruction.newtest2(fieldFactory, config, Mx, chal);

        final R1CSRelationRDD<BNFrT> r1cs = construction._1();
        final Assignment<BNFrT> primary = construction._2();
        final JavaPairRDD<Long, BNFrT> fullAssignment = construction._3();

        long t_zkSetup = System.nanoTime();
        final CRS<BNFrT, BNG1T, BNG2T, BNGTT> CRS =
                DistributedSetup.generate(r1cs, fieldFactory, g1Factory, g2Factory, pairing, config);
        long t_zkSetup_end = System.nanoTime();
        double t_zkSetup_time = (double) (t_zkSetup_end - t_zkSetup) / 1000000000.0;

        long t_zkProof = System.nanoTime();
        final Proof<BNG1T, BNG2T> proof =
                DistributedProver.prove(CRS.provingKeyRDD(), primary, fullAssignment, fieldFactory, config);
        long t_zkProof_end = System.nanoTime();
        System.out.println("zkProof time:" + (double) (t_zkProof_end - t_zkProof) / 1000000000.0 + "s");
        double t_zkProof_time = (double) (t_zkProof_end - t_zkProof) / 1000000000.0;

        long t_zkVerify = System.nanoTime();
        final boolean isValid = Verifier.verify(CRS.verificationKey(), primary, proof, pairing, config);
        long t_zkVerify_end = System.nanoTime();
        System.out.println("zkVerify time:" + (double) (t_zkVerify_end - t_zkVerify) / 1000000000.0 + "s");
        double t_zkVerify_time = (double) (t_zkVerify_end - t_zkVerify) / 1000000000.0;
        System.out.println("zkSetup time:" + (double) (t_zkSetup_end - t_zkSetup) / 1000000000.0 + "s");
        System.out.println(isValid);
        return new DizkData(t_zkSetup_time, t_zkProof_time, t_zkVerify_time);
    }


    //zksnark设置参数，调用方法
    public DizkData SerialBN254aProofSystem(Configuration config, byte[][][] Mx, Map<Integer, Element> chal) {
        final BN254aFields.BN254aFr fieldFactory = BN254aFields.BN254aFr.ONE;
        final BN254aG1 g1Factory = BN254aG1Parameters.ONE;
        final BN254aG2 g2Factory = BN254aG2Parameters.ONE;
        final BN254aPairing pairing = new BN254aPairing();

        return SerialBNProofSystem(config, fieldFactory, g1Factory, g2Factory, pairing, Mx, chal);
    }

    //dizk设置参数，调用方法
    public DizkData DistributedBN254aProofSystem(Configuration config, byte[][][] Mx, Map<Integer, Element> chal) throws IOException {
        final BN254aFields.BN254aFr fieldFactory = new BN254aFields.BN254aFr(1);
        final BN254aG1 g1Factory = BN254aG1Parameters.ONE;
        final BN254aG2 g2Factory = BN254aG2Parameters.ONE;
        final BN254aPairing pairing = new BN254aPairing();

        return DistributedBNProofSystem(config, fieldFactory, g1Factory, g2Factory, pairing, Mx, chal);
    }


    public static void main(String[] args) throws ReedSolomonException, IOException {

        Scheme scheme = new Scheme();
        long t_fileGen = System.nanoTime();
        byte[][][] Mx = Arithmetic.hashFilePolyGen(init.chunckNum, 0, init.blockNum);
        long t_fileGen_end = System.nanoTime();
        double onesecond = 1000000000.0;
        System.out.println("file_ploy_gen time:" + (double) (t_fileGen_end - t_fileGen) / onesecond + "s");
        double t_fileGen_time = (double) (t_fileGen_end - t_fileGen) / onesecond;

        long t_encode = System.nanoTime();
        byte[][][] newMx = scheme.encode(Mx);
        long t_encode_end = System.nanoTime();
        System.out.println("encode time:" + (double) (t_encode_end - t_encode) / onesecond + "s");
        double t_encode_time = (double) (t_encode_end - t_encode) / onesecond;

        long t_init = System.nanoTime();
        scheme.setup(newMx);
        long t_init_end = System.nanoTime();
        System.out.println("Initialize time:" + (double) (t_init_end - t_init) / onesecond + "s");
        double t_init_time = (double) (t_init_end - t_init) / onesecond;

        long t_tag = System.nanoTime();
        scheme.tag_Gen();
        long t_tag_end = System.nanoTime();
        System.out.println("tagGen time:" + (double) (t_tag_end - t_tag) / onesecond + "s");
        double t_tag_time = (double) (t_tag_end - t_tag) / onesecond;

        long t_chal = System.nanoTime();
        Map<Integer, Element> chal = scheme.challenge(init.chunckNum);
        long t_chal_end = System.nanoTime();
        System.out.println("challenge time:" + (double) (t_chal_end - t_chal) / onesecond + "s");
        double t_chal_time = (double) (t_chal_end - t_chal) / onesecond;

        long t_decode = System.nanoTime();
        decodeData decode = scheme.decodeMx(chal);
        long t_decode_end = System.nanoTime();
        System.out.println("decode time:" + (double) (t_decode_end - t_decode) / onesecond + "s");
        double t_decode_time = (double) (t_decode_end - t_decode) / onesecond;

        long t_read = System.nanoTime();
        scheme.read(decode.getWit(), decode.getProd2());
        long t_read_end = System.nanoTime();
        System.out.println("Read time:" + (double)(t_read_end - t_read)/onesecond + "s");
        double t_read_time = (double)(t_read_end - t_read)/onesecond;

        long t_tagGen_HMx = System.nanoTime();
        hashMxGenData hashMxGen_data = scheme.hashMxGen(chal);
        long t_tagGen_HMx_end = System.nanoTime();
        System.out.println("tagGen HMx time:" + (double) (t_tagGen_HMx_end - t_tagGen_HMx) / onesecond + "s");
        double t_hmx_time = (double) (t_tagGen_HMx_end - t_tagGen_HMx) / onesecond;

        long t_proof = System.nanoTime();
        ProofData proofData = scheme.proofGen(chal, hashMxGen_data.getHMx());
        long t_proof_end = System.nanoTime();
        System.out.println("ProofGen time:" + (double) (t_proof_end - t_proof) / onesecond + "s");
        double t_proof_time = (double) (t_proof_end - t_proof) / onesecond;

        long t_verify = System.nanoTime();
        boolean res = scheme.verify(chal, proofData.getTheta_prf(), proofData.getWit(), proofData.getLx_sum());
        long t_verify_end = System.nanoTime();
        System.out.println("Verify time:" + (double) (t_verify_end - t_verify) / onesecond + "s");
        double t_verify_time = (double) (t_verify_end - t_verify) / onesecond;

        System.out.println("verification result: " + res);

        scheme.sparkSetup();
//        Configuration config = scheme.setUp();
//         DizkData dizkData = scheme.SerialBN254aProofSystem(config, Mx, chal);

        DizkData dizkData = scheme.DistributedBN254aProofSystem(scheme.config, Mx, chal);

        //写入excel
        if(init.write == 1){
            if(init.newSheet == 0){
                Workbook workbook = new HSSFWorkbook();
                Sheet sheet = workbook.createSheet();
                Row titleRow = sheet.createRow(0);
                //设置excel的列标题
                String[] title = {"ChunkNum", "blockNum", "t_fileGen_time","t_encode_time","t_init_time", "t_tag_time","t_decode_time","t_chal_time","t_hmx_time",
                        "t_proof_time","t_verify_time", "t_zkSetup_time","t_zkProof_time", "t_zkVerify_time","store", "read", "prove", "verify"};

                for(int i = 0; i < title.length; i++){
                    Cell cell = titleRow.createCell(i);
                    cell.setCellValue(title[i]);
                    FileOutputStream fileOutputStream = new FileOutputStream("s2_yu_b80_node1.xlsx");
                    workbook.write(fileOutputStream);
                }
            } else {
                FileInputStream inputStream = new FileInputStream("s2_yu_b80_node1.xlsx");
                Workbook workbook = new HSSFWorkbook(inputStream);
                Sheet sheet = workbook.getSheetAt(0);
                Row row = sheet.createRow(sheet.getLastRowNum() + 1);

                double[] timRes = {scheme.chunkNum, scheme.blockNum, t_fileGen_time, t_encode_time, t_init_time,t_tag_time,t_decode_time, t_chal_time,t_hmx_time,
                        t_proof_time,t_verify_time,dizkData.getZk_Setup(), dizkData.getZk_Proof(),dizkData.getZk_Verify(), t_tag_time, t_read_time, t_proof_time + dizkData.getZk_Proof(), t_verify_time + dizkData.getZk_Verify()};
                for(int i = 0; i < timRes.length; i++){
                    Cell cell = row.createCell(i);
                    cell.setCellValue(timRes[i]);
                }

                FileOutputStream fileOutputStream = new FileOutputStream("s2_yu_b80_node1.xlsx");
                workbook.write(fileOutputStream);
                fileOutputStream.close();
            }
        }
    }
}
